package examples.jsf.component;

import examples.jsf.AbortProcessingException;
import examples.jsf.context.FacesContext;
import examples.jsf.event.*;

import javax.el.ValueExpression;
import java.io.IOException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

/**
 * @author Bill
 * @version 2013-05-29
 */
public abstract class UIComponent implements PartialStateHolder{

    /**
     * <p>Return the parent {@link UIComponent} of this
     * <code>UIComponent</code>, if any.  A component must allow child
     * components to be added to and removed from the list of children
     * of this component, even though the child component returns null
     * from <code>getParent( )</code>.</p>
     */
    public abstract UIComponent getParent();

    /**
     * <p class="changed_modified_2_0"><span
     * class="changed_modified_2_0_rev_a">Set</span> the parent
     * <code>UIComponent</code> of this <code>UIComponent</code>.  <span
     * class="changed_added_2_0"><span
     * class="changed_modified_2_0_rev_a">If
     * <code>parent.isInView()</code> returns <code>true</code>, calling
     * this method will first cause a {@link
     * javax.faces.event.PreRemoveFromViewEvent} to be published, for
     * this node, and then the children of this node.  Then, once the
     * re-parenting has occurred, a {@link
     * javax.faces.event.PostAddToViewEvent} will be published as well,
     * first for this node, and then for the node's children, <span
     * class="changed_modified_2_0_rev_a">but only if any of the
     * following conditions are true.</span></span></p>

     * <div class="changed_modified_2_0_rev_a">

     *     <ul>

     *       <li><p>{@link
     *       javax.faces.context.FacesContext#getCurrentPhaseId} returns
     *       {@link javax.faces.event.PhaseId#RESTORE_VIEW} and partial
     *       state saving is enabled.</p></li>

     *       <li><p>{@link javax.faces.context.FacesContext#isPostback}
     *       returns <code>false</code> and {@link
     *       javax.faces.context.FacesContext#getCurrentPhaseId} returns
     *       something other than {@link
     *       javax.faces.event.PhaseId#RESTORE_VIEW}</p></li>

     *    </ul>

     * </div>


     * <p class="changed_modified_2_0"> <strong>This method must never
     * be called by developers; a {@link UIComponent}'s internal
     * implementation will call it as components are added to or removed
     * from a parent's child <code>List</code> or facet
     * <code>Map</code></strong></span>.</p>
     *
     * @param parent The new parent, or <code>null</code> for the root node
     *  of a component tree
     */
    public abstract void setParent(UIComponent parent);

    public abstract boolean isRendered();

    public abstract void setRendered(boolean rendered);

    // It is safe to cache this because components never go from being
    // composite to non-composite.
    private transient Boolean isCompositeComponent = null;

    public ValueExpression getValueExpression(String attr) {
        throw new UnsupportedOperationException();
    }


    /**
     * <p class="changed_added_2_0">Return <code>true</code> if
     * <code>component</code> is a composite component, otherwise
     * <code>false</code>.</p>
     *
     * @param component the {@link UIComponent} to test
     *
     * @throws NullPointerException if <code>component</code> is <code>null</code>
     * @since 2.0
     */
    public static boolean isCompositeComponent(UIComponent component) {
        return false;
    }

    /**
     * <p>
     * Finds the nearest composite component parent of the specified component.
     * </p>
     *
     * @param component the component from which to start the search from
     *
     * @return if <code>component</code> is <code>null</code>, return
     *  <code>null</code>, otherwise search the component's parent hierachy
     *  for the nearest parent composite component.  If no parent composite
     *  component is found, return <code>null</code>
     *
     * @since 2.0
     */
    public static UIComponent getCompositeComponentParent(UIComponent component) {
        // 省略
       throw new UnsupportedOperationException();
    }

    //--------------------------------------------------- Event methods

    protected abstract void addFacesListener(FacesListener listener);
    protected abstract FacesListener[] getFacesListeners(Class clazz);
    protected abstract void removeFacesListener(FacesListener listener);

    /**
     * <p>Queue an event for broadcast at the end of the current request
     * processing lifecycle phase.  The default implementation in
     * {@link UIComponentBase} must delegate this call to the
     * <code>queueEvent()</code> method of the parent {@link UIComponent}.</p>
     *
     * @param event {@link FacesEvent} to be queued
     *
     * @throws IllegalStateException if this component is not a
     *  descendant of a {@link UIViewRoot}
     * @throws NullPointerException if <code>event</code>
     *  is <code>null</code>
     */
    public abstract void queueEvent(FacesEvent event);

    public void subscribeToEvent(Class<? extends SystemEvent> eventClass,
                                 ComponentSystemEventListener componentListener) {
        throw new UnsupportedOperationException();
    }
    public void unsubscribeFromEvent(Class<? extends SystemEvent> eventClass,
                                     ComponentSystemEventListener componentListener) {
        throw new UnsupportedOperationException();
    }
    public List<SystemEventListener> getListenersForEventClass(Class<? extends SystemEvent> eventClass) {
        throw new UnsupportedOperationException();
    }

    //----------------------------------------------------

    public abstract Object processSaveState(FacesContext context);

    /**
     * <p><span class="changed_modified_2_0">Perform</span> the
     * component tree processing required by the <em>Apply Request
     * Values</em> phase of the request processing lifecycle for all
     * facets of this component, all children of this component, and
     * this component itself, as follows.</p>

     * <ul>
     * <li>If the <code>rendered</code> property of this {@link UIComponent}
     *     is <code>false</code>, skip further processing.</li>
     * <li class="changed_added_2_0">Call {@link #pushComponentToEL}.</li>
     * <li>Call the <code>processDecodes()</code> method of all facets
     *     and children of this {@link UIComponent}, in the order determined
     *     by a call to <code>getFacetsAndChildren()</code>.</li>

     * <li>Call the <code>decode()</code> method of this component.</li>

     * <li>Call {@link #popComponentFromEL} from inside of a
     * <code>finally block, just before returning.</code></li>



     * <li>If a <code>RuntimeException</code> is thrown during
     *     decode processing, call {@link FacesContext#renderResponse}
     *     and re-throw the exception.</li>
     * </ul>
     *
     * @param context {@link FacesContext} for the request we are processing
     *
     * @throws NullPointerException if <code>context</code>
     *  is <code>null</code>
     */
    public abstract void processDecodes(FacesContext context);

    /**
     * <p><span class="changed_modified_2_0"><span
     * class="changed_modified_2_0_rev_a">Perform</span></span> the component
     * tree processing required by the <em>Process Validations</em>
     * phase of the request processing lifecycle for all facets of this
     * component, all children of this component, and this component
     * itself, as follows.</p>

     * <ul>
     * <li>If the <code>rendered</code> property of this {@link UIComponent}
     *     is <code>false</code>, skip further processing.</li>
     * <li class="changed_added_2_0">Call {@link #pushComponentToEL}.</li>
     * <li>Call the <code>processValidators()</code> method of all facets
     *     and children of this {@link UIComponent}, in the order determined
     *     by a call to <code>getFacetsAndChildren()</code>.</li>

     * <li><span class="changed_modified_2_0_rev_a">After returning from
     * calling <code>getFacetsAndChildren()</code> call {@link
     * UIComponent#popComponentFromEL}.</span></li> </ul>
     *
     * @param context {@link FacesContext} for the request we are processing
     *
     * @throws NullPointerException if <code>context</code>
     *  is <code>null</code>
     */
    public abstract void processValidators(FacesContext context);

    /**
     * <p><span class="changed_modified_2_0">Perform</span> the
     * component tree processing required by the <em>Update Model
     * Values</em> phase of the request processing lifecycle for all
     * facets of this component, all children of this component, and
     * this component itself, as follows.</p>

     * <ul>

     * <li>If the <code>rendered</code> property of this {@link
     * UIComponent} is <code>false</code>, skip further processing.</li>

     * <li class="changed_added_2_0">Call {@link
     * #pushComponentToEL}.</li>

     * <li>Call the <code>processUpdates()</code> method of all facets
     * and children of this {@link UIComponent}, in the order determined
     * by a call to <code>getFacetsAndChildren()</code>.  <span
     * class="changed_added_2_0">After returning from the
     * <code>processUpdates()</code> method on a child or facet, call
     * {@link UIComponent#popComponentFromEL}</span></li>

     * </ul>
     *
     * @param context {@link FacesContext} for the request we are processing
     *
     * @throws NullPointerException if <code>context</code>
     *  is <code>null</code>
     */
    public abstract void processUpdates(FacesContext context);

    // 默认组件的事件处理方法，只处理 SateRestore 事件, RestoreView 阶段会广播此事件
    public void processEvent(ComponentSystemEvent event) throws AbortProcessingException {

        if (event instanceof PostRestoreStateEvent) {
            assert(this == event.getComponent());
            // if this component has a component value reference expression,
            // make sure to populate the ValueExpression for it.
            ValueExpression valueExpression;
            if (null != (valueExpression = this.getValueExpression("binding"))) {
                valueExpression.setValue(FacesContext.getCurrentInstance().getELContext(),
                        this);
            }
            isCompositeComponent = null;
        }

    }


    /**
     * <p><span class="changed_modified_2_0">If</span> our
     * <code>rendered</code> property is <code>true</code>, render the
     * beginning of the current state of this {@link UIComponent} to the
     * response contained in the specified {@link FacesContext}.
     * Call {@link #pushComponentToEL(javax.faces.context.FacesContext,javax.faces.component.UIComponent)}.
     * Call {@link javax.faces.application.Application#publishEvent}, passing
     * {@link javax.faces.event.PreRenderComponentEvent}<code>.class</code> as the
     * first argument and the component instance to be rendered as the
     * second argument.</p></li>

     * <p>If a {@link Renderer} is associated with this {@link
     * UIComponent}, the actual encoding will be delegated to
     * {@link Renderer#encodeBegin(FacesContext, UIComponent)}.
     * </p>
     *
     * <p class="changed_added_2_0">If our <code>rendered</code> property is
     * <code>false</code>, call {@link #pushComponentToEL(javax.faces.context.FacesContext,javax.faces.component.UIComponent)}
     * and return immediately.</p>
     *
     * @param context {@link FacesContext} for the response we are creating
     *
     * @throws IOException if an input/output error occurs while rendering
     * @throws NullPointerException if <code>context</code>
     *  is <code>null</code>
     */
    public abstract void encodeBegin(FacesContext context) throws IOException;


    /**
     * <p>If our <code>rendered</code> property is <code>true</code>,
     * render the child {@link UIComponent}s of this {@link UIComponent}.
     * This method will only be called
     * if the <code>rendersChildren</code> property is <code>true</code>.</p>
     *
     * <p>If a {@link Renderer} is associated with this {@link UIComponent},
     * the actual encoding will be delegated to
     * {@link Renderer#encodeChildren(FacesContext, UIComponent)}.
     * <span class="changed_modified_2_0">If no {@link Renderer} is associated
     * with this {@link UIComponent}, iterate over each of the children of this
     * component and call
     * {@link #encodeAll(javax.faces.context.FacesContext)}.</span></p>
     *
     * @param context {@link FacesContext} for the response we are creating
     *
     * @throws IOException if an input/output error occurs while rendering
     * @throws NullPointerException if <code>context</code>
     *  is <code>null</code>
     */
    public abstract void encodeChildren(FacesContext context) throws IOException;


    /**
     * <p><span class="changed_modified_2_0">If</span> our
     * <code>rendered</code> property is <code>true</code>, render the
     * ending of the current state of this {@link UIComponent}.</p>
     *
     * <p>If a {@link Renderer} is associated with this {@link UIComponent},
     * the actual encoding will be delegated to
     * {@link Renderer#encodeEnd(FacesContext, UIComponent)}.</p>
     *
     * <p class="changed_added_2_0">Call {@link
     * UIComponent#popComponentFromEL}. before returning regardless of the value
     *  of the <code>rendered</code> property.</p>
     *
     * @param context {@link FacesContext} for the response we are creating
     *
     * @throws IOException if an input/output error occurs while rendering
     * @throws NullPointerException if <code>context</code>
     *  is <code>null</code>
     */
    public abstract void encodeEnd(FacesContext context) throws IOException;

    /**
     * <p>If this component
     * returns <code>true</code> from {@link #isRendered}, take the
     * following action.</p>
     *
     * <p>Render this component and all its children that return
     * <code>true</code> from <code>isRendered()</code>, regardless of
     * the value of the {@link #getRendersChildren} flag.</p></li>

     * @since 1.2
     *
     * @throws java.io.IOException if an input/output error occurs while rendering
     * @throws NullPointerException if <code>context</code>
     *  is <code>null</code>
     */
    public void encodeAll(FacesContext context) throws IOException {

        if (context == null) {
            throw new NullPointerException();
        }

        if (!isRendered()) {
            return;
        }

        encodeBegin(context);
//        if (getRendersChildren()) {
//            encodeChildren(context);
//        } else if (this.getChildCount() > 0) {
//            for (UIComponent kid : getChildren()) {
//                kid.encodeAll(context);
//            }
//        }

        encodeEnd(context);

    }


    public final void popComponentFromEL(FacesContext context) {
        throw new UnsupportedOperationException();
    }

    public final void pushComponentToEL(FacesContext context, Object o) {
        throw new UnsupportedOperationException();
    }

    public StateHelper getStateHelper() {
        if (stateHelper == null) {
            stateHelper = new StateHelper();
        }
        return stateHelper;
    }

    private StateHelper stateHelper;

    static class StateHelper {

        private Map _map = new HashMap();

        // get value with default
        public Object eval(Object key, Object defaultValue) {
            if (_map.containsKey(key)) {
                return _map.get(key);
            }
            return defaultValue;
        }

        public Object eval(Object key) {
            return eval(key, null);
        }

        public Object get(Object key) {
            return eval(key);
        }

        public void put(Object key, Object value) {
            _map.put(key, value);
        }
    }


    // ------------------------------------------------- Tree Management Methods


    /**
     * <p><span class="changed_modified_2_0">Return</span> a mutable
     * <code>List</code> representing the child {@link UIComponent}s
     * associated with this component.  The returned implementation must
     * support all of the standard and optional <code>List</code>
     * methods, plus support the following additional requirements:</p>
     * <ul> <li>The <code>List</code> implementation must implement the
     * <code>java.io.Serializable</code> interface.</li> <li>Any attempt
     * to add a <code>null</code> must throw a NullPointerException</li>
     * <li>Any attempt to add an object that does not implement {@link
     * UIComponent} must throw a ClassCastException.</li> <li>Whenever a
     * new child component is added, the <code>parent</code> property of
     * the child must be set to this component instance.  If the
     * <code>parent</code> property of the child was already non-null,
     * the child must first be removed from its previous parent (where
     * it may have been either a child or a facet).</li> <li>Whenever an
     * existing child component is removed, the <code>parent</code>
     * property of the child must be set to <code>null</code>.</li>

     * <li class="changed_modified_2_1"><p>After the child component has
     *     been added to the view, {@link
     *     javax.faces.application.Application#publishEvent} must be
     *     called, passing {@link
     *     javax.faces.event.PostAddToViewEvent}<code>.class</code> as
     *     the first argument and the newly added component as the
     *     second argument if any the following cases are true.</p>
     *
     *     <ul>

     *       <li><p>{@link
     *       javax.faces.context.FacesContext#getCurrentPhaseId} returns
     *       {@link javax.faces.event.PhaseId#RESTORE_VIEW} and partial
     *       state saving is enabled.</p></li>

     *       <li><p>{@link javax.faces.context.FacesContext#isPostback}
     *       returns <code>false</code> and {@link
     *       javax.faces.context.FacesContext#getCurrentPhaseId} returns
     *       something other than {@link
     *       javax.faces.event.PhaseId#RESTORE_VIEW}</p></li>

     *    </ul>

     * </li>

     * </ul>
     */
    public abstract List<UIComponent> getChildren();


    /**
     * <p>Return the number of child {@link UIComponent}s that are
     * associated with this {@link UIComponent}.  If there are no
     * children, this method must return 0.  The method must not cause
     * the creation of a child component list.</p>
     */
    public abstract int getChildCount();


    /**
     * <p>Search for and return the {@link UIComponent} with an <code>id</code>
     * that matches the specified search expression (if any), according to the
     * algorithm described below.</p>
     *
     * <p>For a method to find a component given a simple
     * <code>clientId</code>, see {@link #invokeOnComponent}.</p>
     *
     * <p>Component identifiers are required to be unique within the scope of
     * the closest ancestor {@link NamingContainer} that encloses this
     * component (which might be this component itself).  If there are no
     * {@link NamingContainer} components in the ancestry of this component,
     * the root component in the tree is treated as if it were a
     * {@link NamingContainer}, whether or not its class actually implements
     * the {@link NamingContainer} interface.</p>
     *
     * <p>A <em>search expression</em> consists of either an identifier
     * (which is matched exactly against the <code>id</code> property of
     * a {@link UIComponent}, or a series of such identifiers linked by
     * the {@link UINamingContainer#getSeparatorChar} character value.
     * The search algorithm should operates as follows, though alternate
     * alogrithms may be used as long as the end result is the same:</p>

     * <ul>
     * <li>Identify the {@link UIComponent} that will be the base for searching,
     *     by stopping as soon as one of the following conditions is met:
     *     <ul>
     *     <li>If the search expression begins with the the separator character
     *         (called an "absolute" search expression),
     *         the base will be the root {@link UIComponent} of the component
     *         tree.  The leading separator character will be stripped off,
     *         and the remainder of the search expression will be treated as
     *         a "relative" search expression as described below.</li>
     *     <li>Otherwise, if this {@link UIComponent} is a
     *         {@link NamingContainer} it will serve as the basis.</li>
     *     <li>Otherwise, search up the parents of this component.  If
     *         a {@link NamingContainer} is encountered, it will be the base.
     *         </li>
     *     <li>Otherwise (if no {@link NamingContainer} is encountered)
     *         the root {@link UIComponent} will be the base.</li>
     *     </ul></li>
     * <li>The search expression (possibly modified in the previous step) is now
     *     a "relative" search expression that will be used to locate the
     *     component (if any) that has an <code>id</code> that matches, within
     *     the scope of the base component.  The match is performed as follows:
     *     <ul>
     *     <li>If the search expression is a simple identifier, this value is
     *         compared to the <code>id</code> property, and then recursively
     *         through the facets and children of the base {@link UIComponent}
     *         (except that if a descendant {@link NamingContainer} is found,
     *         its own facets and children are not searched).</li>
     *     <li>If the search expression includes more than one identifier
     *         separated by the separator character, the first identifier is
     *         used to locate a {@link NamingContainer} by the rules in the
     *         previous bullet point.  Then, the <code>findComponent()</code>
     *         method of this {@link NamingContainer} will be called, passing
     *         the remainder of the search expression.</li>
     *     </ul></li>
     * </ul>
     *
     * @param expr Search expression identifying the {@link UIComponent}
     *  to be returned
     *
     * @return the found {@link UIComponent}, or <code>null</code>
     *  if the component was not found.
     *
     * @throws IllegalArgumentException if an intermediate identifier
     *  in a search expression identifies a {@link UIComponent} that is
     *  not a {@link NamingContainer}
     * @throws NullPointerException if <code>expr</code>
     *  is <code>null</code>
     */
    public abstract UIComponent findComponent(String expr);


    // ------------------------------------------------ Facet Management Methods

    /**
     * <p>Return a mutable <code>Map</code> representing the facet
     * {@link UIComponent}s associated with this {@link UIComponent},
     * keyed by facet name (which must be a String).  The returned
     * implementation must support all of the standard and optional
     * <code>Map</code> methods, plus support the following additional
     * requirements:</p>

     * <ul>
     * <li>The <code>Map</code> implementation must implement
     *     the <code>java.io.Serializable</code> interface.</li>
     * <li>Any attempt to add a <code>null</code> key or value must
     *     throw a NullPointerException.</li>
     * <li>Any attempt to add a key that is not a String must throw
     *     a ClassCastException.</li>
     * <li>Any attempt to add a value that is not a {@link UIComponent}
     *     must throw a ClassCastException.</li>
     * <li>Whenever a new facet {@link UIComponent} is added:
     *     <ul>
     *     <li>The <code>parent</code> property of the component must be set to
     *         this component instance.</li>
     *     <li>If the <code>parent</code> property of the component was already
     *     non-null, the component must first be removed from its previous
     *     parent (where it may have been either a child or a facet).</li>
     *     </ul></li>

     * <li>Whenever an existing facet {@link UIComponent} is removed:
     *     <ul>
     *     <li>The <code>parent</code> property of the facet must be
     *         set to <code>null</code>.</li>
     *     </ul></li>
     * </ul>
     */
    public abstract Map<String, UIComponent> getFacets();

    /**
     * <p>Return the number of facet {@link UIComponent}s that are
     * associated with this {@link UIComponent}.  If there are no
     * facets, this method must return 0.  The method must not cause
     * the creation of a facet component map.</p>
     *
     * <p>For backwards compatability with classes that extend UIComponent
     * directly, a default implementation is provided that simply calls
     * {@link #getFacets} and then calls the <code>size()</code> method on the
     * returned <code>Map</code>.  A more optimized version of this method is
     * provided in {@link UIComponentBase#getFacetCount}.
     *
     * @since 1.2
     */
    public int getFacetCount() {
        return (getFacets().size());
    }

    /**
     * <p>Convenience method to return the named facet, if it exists, or
     * <code>null</code> otherwise.  If the requested facet does not
     * exist, the facets Map must not be created.</p>
     *
     * @param name Name of the desired facet
     */
    public abstract UIComponent getFacet(String name);


    /**
     * <p>Return an <code>Iterator</code> over the facet followed by child
     * {@link UIComponent}s of this {@link UIComponent}.
     * Facets are returned in an undefined order, followed by
     * all the children in the order they are stored in the child list. If this
     * component has no facets or children, an empty <code>Iterator</code>
     * is returned.</p>
     *
     * <p>The returned <code>Iterator</code> must not support the
     * <code>remove()</code> operation.</p>
     */
    public abstract Iterator<UIComponent> getFacetsAndChildren();


    // -------------------------------------------- Lifecycle Processing Methods


    /**
     * <p>Broadcast the specified {@link FacesEvent} to all registered
     * event listeners who have expressed an interest in events of this
     * type.  Listeners are called in the order in which they were
     * added.</p>
     * <p class="changed_added_2_0">If the <code>event</code> is an instance of
     * {@link javax.faces.event.BehaviorEvent} and the current
     * <code>component</code> is the source of the <code>event</code>
     * call {@link javax.faces.event.BehaviorEvent#getBehavior} to get the
     * {@link javax.faces.component.behavior.Behavior} for the event.

     * <span class="changed_modified_2_0_rev_a">Call {@link
     * javax.faces.component.behavior.Behavior#broadcast(javax.faces.event.BehaviorEvent)}
     * on the <code>Behavior</code> instance</span>.</p>
     *
     * @param event The {@link FacesEvent} to be broadcast
     *
     * @throws AbortProcessingException Signal the JavaServer Faces
     *  implementation that no further processing on the current event
     *  should be performed
     * @throws IllegalArgumentException if the implementation class
     *  of this {@link FacesEvent} is not supported by this component
     * @throws NullPointerException if <code>event</code> is
     * <code>null</code>
     */
    public abstract void broadcast(FacesEvent event)
            throws AbortProcessingException;


    /**
     * <p>Decode any new state of this {@link UIComponent} from the
     * request contained in the specified {@link FacesContext}, and store
     * this state as needed.</p>
     * <p>During decoding, events may be enqueued for later processing
     * (by event listeners who have registered an interest),  by calling
     * <code>queueEvent()</code>.</p>
     *
     * @param context {@link FacesContext} for the request we are processing
     *
     * @throws NullPointerException if <code>context</code>
     *  is <code>null</code>
     */
    public abstract void decode(FacesContext context);



    //------------------------------------------ Partial State Management

    @Override
    public void markInitialState() {
        //To change body of implemented methods use File | Settings | File Templates.
    }

    @Override
    public boolean initialStateMarked() {
        return false;  //To change body of implemented methods use File | Settings | File Templates.
    }

    @Override
    public void clearInitialState() {
        //To change body of implemented methods use File | Settings | File Templates.
    }

}
